
package com.mhy.soloader.so;

import android.content.Context;
import android.os.Build;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;

/**
 * 设置指定的目录为so加载目录
 */
public class NativeTool {
    /**
     * 将path设置为当前应用的so载入路径（path下直接包含若干个so文件）， android应用的so文件路径修改,添加自定义的so路径
     */
    public static void SetSoDirectory(Context context, String path) {
        if (hasDexClassLoader()) {
            try {
                createNewNativeDirAboveEqualApiLevel14(context, path);
            } catch (Exception e) {
                try {
                    createNewNativeDirAboveEqualApiLevel21(context, path);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        } else {
            try {
                createNewNativeDirBelowApiLevel14(context, path);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 4.0以上系统添加方案
     *
     * @param context
     * @param path    自定义so路径
     * @throws Exception
     */
    private static void createNewNativeDirAboveEqualApiLevel14(Context context, String path) throws Exception {
        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
        Object pathList = getPathList(pathClassLoader);
        // 获取当前类的属性
        Object nativeLibraryDirectories = pathList.getClass().getDeclaredField("nativeLibraryDirectories");
        ((Field) nativeLibraryDirectories).setAccessible(true);
        // 获取 DEXPATHList中的属性值
        File[] files = (File[]) ((Field) nativeLibraryDirectories).get(pathList);
        Object newfiles = Array.newInstance(File.class, files.length + 1);
        // 添加自定义.so路径
        Array.set(newfiles, 0, new File(path));
        // 追加系统路径
        for (int i = 1; i < files.length + 1; i++) {
            Array.set(newfiles, i, files[i - 1]);
        }
        ((Field) nativeLibraryDirectories).set(pathList, newfiles);
    }

    /**
     * 4.0以下系统添加方案
     *
     * @param context
     * @param path    自定义so路径
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static void createNewNativeDirBelowApiLevel14(Context context, String path) throws NoSuchFieldException, IllegalAccessException {
        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
        Field mLibPaths = pathClassLoader.getClass().getDeclaredField("mLibPaths");
        mLibPaths.setAccessible(true);
        String[] libs = (String[]) (mLibPaths).get(pathClassLoader);
        Object newPaths = Array.newInstance(String.class, libs.length + 1);
        // 添加自定义.so路径
        Array.set(newPaths, 0, path);
        // 追加系统路径
        for (int i = 1; i < libs.length + 1; i++) {
            Array.set(newPaths, i, libs[i - 1]);
        }
        mLibPaths.set(pathClassLoader, newPaths);
    }

    /**
     * 5.0以上系统添加方案
     *
     * @param context
     * @param path    自定义so路径
     * @throws IllegalAccessException
     * @throws NoSuchFieldException
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    private static void createNewNativeDirAboveEqualApiLevel21(Context context, String path) throws IllegalAccessException, NoSuchFieldException,
            ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        if (Build.VERSION.SDK_INT < 21/* Build.VERSION_CODES.LOLLIPOP */) {
            return;
        }
        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
        Object pathList = getPathList(pathClassLoader);
        // 获取当前类的属性
        Object systemNativeLibraryDirectories = pathList.getClass().getDeclaredField("systemNativeLibraryDirectories");
        ((Field) systemNativeLibraryDirectories).setAccessible(true);
        List<File> systemFiles = (List<File>) ((Field) systemNativeLibraryDirectories).get(pathList);
        systemFiles.add(new File(path));
        ((Field) systemNativeLibraryDirectories).set(pathList, systemFiles);

        Object nativeLibraryDirectories = pathList.getClass().getDeclaredField("nativeLibraryDirectories");
        ((Field) nativeLibraryDirectories).setAccessible(true);
        List<File> nativeFiles = (ArrayList<File>) ((Field) nativeLibraryDirectories).get(pathList);
        nativeFiles.add(new File(path));
        ((Field) nativeLibraryDirectories).set(pathList, nativeFiles);

        Class<?> elementClass = Class.forName("dalvik.system.DexPathList$Element");
        Constructor<?> element = null;
        element = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
        Object nativeLibraryPathElements = pathList.getClass().getDeclaredField("nativeLibraryPathElements");
        ((Field) nativeLibraryPathElements).setAccessible(true);
        Object[] elementFiles = (Object[]) ((Field) nativeLibraryPathElements).get(pathList);
        Object newElementFiles = Array.newInstance(elementClass, elementFiles.length + 1);
        if (element != null) {
            try {
                Object newInstance = element.newInstance(new File(path), true, null, null);
                Array.set(newElementFiles, 0, newInstance);
                for (int i = 1; i < elementFiles.length + 1; i++) {
                    Array.set(newElementFiles, i, elementFiles[i - 1]);
                }
                ((Field) nativeLibraryPathElements).set(pathList, newElementFiles);
            } catch (java.lang.IllegalArgumentException e) {
                Method elements = pathList.getClass().getDeclaredMethod("makePathElements", List.class);
                elements.setAccessible(true);
                Object invoke = elements.invoke(null, nativeFiles);

                Object nativeLibraryElements = pathList.getClass().getDeclaredField("nativeLibraryPathElements");
                ((Field) nativeLibraryElements).setAccessible(true);
                ((Field) nativeLibraryElements).set(pathList, invoke);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获取 pathList
     *
     * @param obj
     * @return
     * @throws ClassNotFoundException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        return getField(obj, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }

    /**
     * 通过反射获取成员变量
     *
     * @param obj
     * @param cls
     * @param str
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static Object getField(Object obj, Class cls, String str) throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cls.getDeclaredField(str);
        declaredField.setAccessible(true);
        return declaredField.get(obj);
    }

    /**
     * 仅对4.0以上做支持
     *
     * @return
     */
    private static boolean hasDexClassLoader() {
        try {
            Class.forName("dalvik.system.BaseDexClassLoader");
            return true;
        } catch (ClassNotFoundException var1) {
            return false;
        }
    }

}
